import { Queue } from "bullmq";
import { Redis } from "ioredis";

export class QueueManager {
  private static instance: QueueManager;
  private queue: Queue | null = null;
  private backupQueue: Queue | null = null;

  private mainConnection: Redis | null = null;
  private backupConnection: Redis | null = null;

  // 添加一个标志，表示是否在开发环境中
  private isDevelopment: boolean = process.env.NODE_ENV === "development";

  private constructor() {
    this.initializePromise = this.initQueue();
  }

  private initializePromise: Promise<void>;
  public static async getInstance(): Promise<QueueManager> {
    if (!QueueManager.instance) {
      QueueManager.instance = new QueueManager();
    }
    // Wait for initialization to complete
    await QueueManager.instance.initializePromise;
    return QueueManager.instance;
  }

  // 1. Create main redis connection
  private async pingRedis({
    useBackup,
    keepConnection = false,
  }: {
    useBackup: boolean;
    keepConnection?: boolean;
  }) {
    // 1. Connect to redis

    const redisUrl = useBackup
      ? process.env.REDIS_BACKUP_URL
      : process.env.REDIS_URL;

    try {
      const connection = new Redis(redisUrl!, {
        retryStrategy: (times) => {
          return 5000;
        },
      });

      connection.on("error", (error) => {
        console.log(
          `Redis connection error (${useBackup ? "backup" : "main"}): ${
            error.message
          }`,
        );

        // 在开发环境中，不会因为 Redis 连接错误而退出
        if (!keepConnection && !this.isDevelopment) {
          process.exit(1);
        }
      });

      // Check if connection is live...
      try {
        await connection.ping();
      } catch (error) {
        console.error(`Redis ping failed (${useBackup ? "backup" : "main"})`);
        if (!this.isDevelopment) {
          throw error;
        } else {
          console.warn(
            "Redis not available, continuing in development mode without Redis",
          );
          return null;
        }
      }

      if (!keepConnection) {
        await connection.quit();
      }
      return connection;
    } catch (error) {
      console.error(
        `Failed to connect to Redis (${useBackup ? "backup" : "main"}): ${error}`,
      );
      if (this.isDevelopment) {
        console.warn(
          "Redis not available, continuing in development mode without Redis",
        );
        return null;
      }
      throw error;
    }
  }

  private async createConnections() {
    console.log("2. Creating redis connections (for workers...)");

    try {
      this.mainConnection = await this.pingRedis({
        useBackup: false,
        keepConnection: true,
      });
    } catch (error) {
      console.error("Failed to create main Redis connection");
      if (!this.isDevelopment) {
        throw error;
      }
    }

    try {
      this.backupConnection = await this.pingRedis({
        useBackup: true,
        keepConnection: true,
      });
    } catch (error) {
      console.error("Failed to create backup Redis connection");
      if (!this.isDevelopment) {
        throw error;
      }
    }
  }

  private async initQueue() {
    console.log("Initializing Queue Manager...");
    console.group();

    try {
      // 1. Create redis connections
      console.log("1. Pinging main & backup redis");

      try {
        this.mainConnection = await this.pingRedis({ useBackup: false });
      } catch (error) {
        if (!this.isDevelopment) throw error;
      }

      try {
        this.backupConnection = await this.pingRedis({ useBackup: true });
      } catch (error) {
        if (!this.isDevelopment) throw error;
      }

      await this.createConnections();

      // 只有在非开发环境或 Redis 可用时才初始化队列
      if (
        !this.isDevelopment ||
        (this.mainConnection && this.backupConnection)
      ) {
        // 2. Initialize main and backup queues
        console.log("2. Initializing main & backup queues");

        if (this.mainConnection) {
          const mainQueue = new Queue("autumn", {
            connection: {
              url: process.env.REDIS_URL,
              enableOfflineQueue: false,
              retryStrategy: (times) => {
                return 5000;
              },
            },
          });

          // Set up error handling for the queue
          mainQueue.on("error", async (error: any) => {
            console.error("QUEUE ERROR:", error.message);
          });

          this.queue = mainQueue;
        }

        if (this.backupConnection) {
          const backupQueue = new Queue("autumn", {
            connection: {
              url: process.env.REDIS_BACKUP_URL,
              enableOfflineQueue: false,
            },
          });

          backupQueue.on("error", async (error: any) => {
            console.error("BACKUP QUEUE ERROR:", error.message);
          });

          this.backupQueue = backupQueue;
        }
      } else {
        console.warn(
          "Skipping queue initialization in development mode without Redis",
        );
      }
    } catch (error) {
      console.error("Error initializing Queue Manager:", error);
      if (!this.isDevelopment) {
        throw error;
      }
    }

    console.groupEnd();
  }

  // Create workers

  public static async getQueue({
    useBackup,
  }: {
    useBackup: boolean;
  }): Promise<Queue> {
    const queueManager = await QueueManager.getInstance();
    if (!queueManager.queue || !queueManager.backupQueue) {
      throw new Error("Queue not initialized");
    }

    return useBackup ? queueManager.backupQueue : queueManager.queue;
  }

  public static async getConnection({
    useBackup,
  }: {
    useBackup: boolean;
  }): Promise<Redis> {
    const queueManager = await QueueManager.getInstance();
    if (!queueManager.mainConnection || !queueManager.backupConnection) {
      throw new Error("Connection not initialized");
    }
    return useBackup
      ? queueManager.backupConnection
      : queueManager.mainConnection;
  }

  public getBackupConnection(): Redis {
    if (!this.backupConnection) {
      throw new Error("Backup connection not initialized");
    }
    return this.backupConnection;
  }
}
